﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;

namespace Aocsoft.Infrastructure.Repository.NHContext
{
    public static class MSessionFactory
    {
        public const string DefaultSessionTypeName = "default";

        private static ILogger logger = LoggerContext.LoggerFactory.GetCurrentClassLogger();

        private readonly static object instanceLockObj = new Object();

        private static string _sessionStoreKey = "SESSION_STORE_KEY_";

        private static Dictionary<string, Configuration> _configurations;
        public static Dictionary<string, Configuration> Configurations
        {
            get
            {
                if (_configurations == null)
                    _configurations = new Dictionary<string, Configuration>();
                return _configurations;
            }
        }
        public static Configuration DefaultConfiguration
        {
            get
            {
                return Configurations[DefaultSessionTypeName];
            }
        }

        private static Dictionary<string, ISessionFactory> _sessionFactories;
        public static Dictionary<string, ISessionFactory> SessionFactories
        {
            get
            {
                if (_sessionFactories == null)
                    _sessionFactories = new Dictionary<string, ISessionFactory>();
                return _sessionFactories;
            }
        }

        private static Dictionary<string, string> _sessionConfigurationFiles;
        private static Dictionary<string, string> SessionConfigurationFiles
        {
            get
            {
                if (_sessionConfigurationFiles == null)
                    _sessionConfigurationFiles = new Dictionary<string, string>();
                return _sessionConfigurationFiles;
            }
        }
        public static void RegisterSessionType(string configurationFile)
        {
            RegisterSessionType(DefaultSessionTypeName, configurationFile);
        }
        public static void RegisterSessionType(string sessionTypeName, string configurationFile)
        {
            if (SessionConfigurationFiles.ContainsKey(sessionTypeName))
            {
                if (SessionConfigurationFiles[sessionTypeName].Equals(configurationFile))
                {
                    logger.InfoFormat("Already register for {0}.", sessionTypeName);
                    return;
                }
                else if (!SessionConfigurationFiles[sessionTypeName].Equals(configurationFile))
                {
                    logger.FatalFormat("The typename of {0} already register, but the connection string was not same.", sessionTypeName);
                }
            }
            SessionConfigurationFiles.Add(sessionTypeName, configurationFile);
        }

        public static ISession GetSessionInstance(string sessionType = DefaultSessionTypeName)
        {
            var session = GetSessionFromCache(sessionType);
            if (session == null)
            {
                Initial(sessionType);
                session = SessionFactories[sessionType].OpenSession();
                session.FlushMode = FlushMode.Commit;
                CacheSession(sessionType, session);
            }
            return GetSessionFromCache(sessionType);
        }

        public static ISession GetNewSessionInstance(string sessionType = DefaultSessionTypeName)
        {
            if (!SessionFactories.ContainsKey(sessionType))
            {
                Initial(sessionType);
            }
            return SessionFactories[sessionType].OpenSession();
        }

        public static List<string> GetRegistedSessionType
        {
            get { return Configurations.Keys.ToList(); }
        }

        private static void CacheSession(string sessionType, ISession session)
        {
            ContextStorage.Save(_sessionStoreKey + sessionType, session);
        }

        private static ISession GetSessionFromCache(string sessionType)
        {
            return ContextStorage.Load<ISession>(_sessionStoreKey + sessionType);
        }

        public static void Initial(string sessionType = DefaultSessionTypeName)
        {
            lock (instanceLockObj)
            {
                if (!SessionFactories.ContainsKey(sessionType))
                {
                    if (!Configurations.ContainsKey(sessionType))
                    {
                        if (!SessionConfigurationFiles.ContainsKey(sessionType))
                        {
                            throw new Exception("Didn't register the session configuration infor for type \" " + sessionType + "\"");
                        }
                        else if (!string.IsNullOrEmpty(SessionConfigurationFiles[sessionType]) && File.Exists(SessionConfigurationFiles[sessionType]))
                        {
                            Configurations.Add(sessionType, new Configuration());
                            var db_prefix = System.Configuration.ConfigurationManager.AppSettings["DB_Prefix"];
                            if (!string.IsNullOrWhiteSpace(db_prefix))
                            {
                                Configurations[sessionType].SetNamingStrategy(new TablePrefixNameStranstage(db_prefix));
                            }
                            var config = Configurations[sessionType].Configure(SessionConfigurationFiles[sessionType]);
                            BuildConfiguration(config);
                        }
                        else
                        {
                            logger.FatalFormat("Can't find the configuration file for session type {0}", sessionType);
                            throw new Exception("Can't find the configuration file for session type\" " + sessionType + "\"");
                        }
                    }
                    try
                    {
                        SessionFactories[sessionType] = Configurations[sessionType].BuildSessionFactory();
                    }
                    catch (Exception ex)
                    {
                        throw new Exception(ex.FullMsg());
                    }
                }
            }
        }

        /// <summary>
        /// Bind connection string from web.config
        /// Hibernate config level is high than web.config
        /// </summary>
        /// <param name="config"></param>
        public static void BuildConfiguration(Configuration config)
        {
            var connectionString = string.Empty;
            if (config.Properties.Keys.Contains("connection.connection_string"))//hibernate config level than web.config.
                connectionString = config.Properties["connection.connection_string"];
            else if (System.Configuration.ConfigurationManager.ConnectionStrings.Count > 0)
            {
                connectionString = System.Configuration.ConfigurationManager.ConnectionStrings[0].ConnectionString;
            }

            if (connectionString.Contains("{apppath}"))
                connectionString = connectionString.Replace("{apppath}", System.AppDomain.CurrentDomain.BaseDirectory);
            logger.InfoFormat("Configure NHibernate use default config file and conn string:{0}", connectionString);

            config.DataBaseIntegration(db =>
            {
                if (!string.IsNullOrWhiteSpace(connectionString))
                    db.ConnectionString = connectionString;
            });
        }
    }

    public static partial class ExtensitionMethod
    {
        public static void UpdateGradeDatabase(this NHibernate.Cfg.Configuration config)
        {
            SchemaUpdate update = new SchemaUpdate(config);
            update.Execute(true, true);
        }

        public static void ReCreateDatabase(this NHibernate.Cfg.Configuration config)
        {
            SchemaExport export = new SchemaExport(config);
            export.Execute(true, true, false);
        }
    }

    public class TablePrefixNameStranstage : INamingStrategy
    {
        public string Table_PrefixName { get; private set; }
        public TablePrefixNameStranstage(string prefixName)
        {
            this.Table_PrefixName = prefixName;
        }

        public string ClassToTableName(string className)
        {
            return string.Format("{0}_{1}", Table_PrefixName, DefaultNamingStrategy.Instance.ClassToTableName(className).Replace("`", string.Empty));
        }

        public string ColumnName(string columnName)
        {
            return DefaultNamingStrategy.Instance.ColumnName(columnName);
        }

        public string LogicalColumnName(string columnName, string propertyName)
        {
            return DefaultNamingStrategy.Instance.LogicalColumnName(columnName, propertyName);
        }

        public string PropertyToColumnName(string propertyName)
        {
            return DefaultNamingStrategy.Instance.PropertyToColumnName(propertyName);
        }

        public string PropertyToTableName(string className, string propertyName)
        {
            return DefaultNamingStrategy.Instance.PropertyToTableName(className, propertyName);
        }

        public string TableName(string tableName)
        {
            return string.Format("{0}_{1}", Table_PrefixName, DefaultNamingStrategy.Instance.TableName(tableName).Replace("`", string.Empty));
        }
    }
}
